Report on Unlikely, 2026
========================

<!--
Copyright (c) 2026, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license, see LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Unlikely
-->

The **Unlikely** programming language has been in existence for many years
now, but has remained largely unimplemented, with there being only a
parser/static analyzer, and the beginnings of a sketch of an interpreter.

In 2026 I decided that a proper implementation of this language was far
overdue, so I started developing an interpreter.

This led to the discovery of several things about the existing state of
Unlikely, which I'll chronicle in this document.

Parser/Static Analyzer
----------------------

### Minor parsing bugs

First, due to an unforgivingness in the lexical tokenizer, every Unlikely
source file needed to have some whitespace at the end of the file, or
it wouldn't parse.  This is minor and has been fixed.

There was also an error in the parser which prevented the correct
parsing of lists of arguments to a method (it wasn't consuming the comma
separating these arguments).  This is also minor and has been fixed.

### Identifier scope in continues

A weirder situation was in the parsing of expressions.  The parser itself
does some lexical analysis as it parses; one of these things is resolving
identifiers in expressions.  It resolves them based on the scope of the
containing method's class definition.

However, when parsing a continue (e.g. `goto foo.continue(bar)`), the
"containing method" of the expressions was considered to be the method
*being called* rather than the method of which the continue is part.

This limits the things that can be passed to the method to newly-constructed
instances and properties that are defined on the destination class whose
names coincide with properties defined on the source class.  It looks like
it was not noticed, because all continues in example programs and tests do
actually fall into these two categories.

This goes against the usual intuitions of passing values to a function -
not that that should be the deciding factor, in an experimental language -
but since the spec does not mention this as a defining feature, and since
it is quite limiting and seems to go against the usual idea of parameter
passing quite strongly, I've treated it as a bug and fixed it.

### Dependency injection

I'll write about this more fully under "Language Definition", but the
static analyzer tries to resolve injected classes without actually being
able to do so.  Dependency injection is usually a runtime thing, but it
is a "more static" concept in Unlikely, in the sense that only classes
can be injected, and there are no class variables.  But to treat it
statically, one would need to examine each *instantiation site* of a
class, and look at what dependant classes it is injecting.  Based on the
comments in the static analyzer, the author (me) clearly thought that
the static analysis was accomplishing that, but it isn't accomplishing
that.

This is probably not worth fixing in the static analyzer since we can
also handle dependency injection at runtime (even though it is
theoretically analyzable ahead-of-time) and that is what I've done in
the interpreter.

Interpreter
-----------

### Persistence of properties

Only a sketch of an interpreter existed, but its code suggested that,
every time a method is executed, not only are the class properties
associated with the arguments populated with the actual arguments passed
in, *all* properties on the object are initialized to default values.
The resulting behaviour is that properties can't reliably persist on objects.

It's very likely this was just an error in the sketch, and the initialization
should have been located elsewhere.

The language description does not say that properties should persist between
invokations of their methods.  The `Loop` built-ins do seem to assume their
state persists between invokations.  But those are built-ins, which are
already assumed to have functionality that pure Unlikely programs don't have.
But in the discussion it is mentioned that they could probably be implemented
in pure Unlikely.

From an object-oriented point of view, it is surprising for object state to
fail to persist.  On the other hand, if we look at these things as
continuations, it is less surprising.  Taking it as a restriction leads to a
more state-passing, functional style, and if we disallow property persistence,
it might lead to a more "interesting" style of programs, where data is
shuttled around because it can't be kept in a Continuation object and mutated.

However, on the balance of what the spec suggests, it probably really was
just an error in the sketch, and properties are expected to persist.  I have
implemented the interpreter accordingly, but left in the original logic
from the sketch under an internal, disabled flag called `AMNESIAC_MODE`.

Language Definition
-------------------

### Dependency injection

Given that built-in classes need not be implemented in Unlikely, it's
unclear why any of them name any dependant classes.  (When I instantiate
`Print`, should I expect to be able to pass it a subclass of `Chain` and
have it use that subclass' behavior instead?  How does `Print` use `Chain`,
anyway?  I mean, doesn't this need to be specified, in order for it to make
sense for me to inject a subclass of `Chain` in an informed fashion?)

Under "Inheritance", the spec says "The subclass may not inject dependencies
when inheriting from a superclass."  This could stand being clarified,
because "injection" is something that happens at instantiation sites, not
class definitions.  I believe it was meant to impart that a subclass cannot
override or modify the dependencies that the superclass has already declared.
Indeed, if it tries to name a dependant class that one of its superclasses
had already named, the static analyzer rejects this as an error.  So it
cannot even bring in two dependencies for the same class, and allow clients
to inject them independently (which would arguably make sense.)

I have added some unit tests for subclassing and dependency injection, to
try to clarify the behaviour.  It appears that the static analyzer is not
incorrect, but the behaviour may be unexpected, due to built-ins having
dependant classes (including `Passive`, which itself feels too simple to be
a dependant - but it's not concrete, like its subclasses, so it is), and
depedants being given in the order they are listed in the class declaration,
and due to already-fixed dependants being skipped in that list.

### Overriding methods

Under "Inheritance", the spec says "A subclass may override methods that it
inherits from its superclass", and a few other things about overriding; but
in the "Discussion" section we see: "Because properties cannot be redefined
in subclasses, and because parameters to methods are just syntactic sugar
for properties, methods cannot be overloaded."  Which, if true, would sort
of push the design away from an experiment and closer to a farce.  But, it's
not strictly true: you can overload a method, you just can't change its
signature.

This could probably use a unit test or two to directly confirm the behaviour.

### Built-in classes

#### Loop

In writing the interpreter I've left out `ForLoop` or `WhileLoop`.  They're
not necessary for looping (see `countdown.unlikely`), their design is suspect
(the design of `WhileLoop` in particular seems particularly goofy), and I have
it on good authority that it's possible to write them in pure Unlikely.
Wrtiting them in pure Unlikely is thus left as an exercise to the reader.

#### Stop

Another decision taken when building the interpreter was that if `Stop`
receives an integer, then that integer becomes the exit code; but if it
receives a value of some other type, the exit code is 0.  This was mainly to
keep the examples simple.

Example programs
----------------

`countdown.unlikely` was written incorrectly.  This has been fixed.  Its
existence substitutes for a Turing-completeness proof, since it should be
fairly straightforward to look at it and see how one could construct an
arbitrary [Minsky machine](https://esolangs.org/wiki/Minsky_machine) in
Unlikely.  (The spec doesn't say integers are unbounded, but it also
doesn't say they aren't.)

`echo.unlikely` and `cat.unlikely` were added to demonstrate `Input`.

`fibonacci.unlikely`, which has historically been incomplete, a
"suggested serving" perhaps, was removed.  This was after a second attempt
to write it was undertaken, then abandoned.  Writing Fibonacci in
continuation-passing style is somewhat abstruse, but not outlandishly so.
But the functions in it are mutually recursive.  In Unlikely, one
would need to use the "forward declaration" feature to tie them
together; however, because they must also declare their dependant classes,
which are also mutually recursive, we must do something tricky to
get around that.  The "obvious" way to get around it - maybe - would be to
define generic base classes that are mutually recursive, and then inject
subclasses of those base classes; but this is definitely adding abstruse
on top of abstruse, and it's not something I've yet had the patience to
pull off.  But, again, I have it on good authority that it is possible
to write it (directly in mutually-recursive continuation-passing style)
in Unlikely, so again, it is left as an exercise to the (motivated) reader.
